package org.apache.ibatis.type;

import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.io.Resources;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.util.*;

/**
 * @author Clinton Begin
 */
public class TypeAliasRegistry {

    private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

    public TypeAliasRegistry() {
        // 注册 String 的别名
        registerAlias("string", String.class);

        // 注册基本类型包装类的别名
        registerAlias("byte", Byte.class);
        registerAlias("long", Long.class);
        registerAlias("short", Short.class);
        registerAlias("int", Integer.class);
        registerAlias("integer", Integer.class);
        registerAlias("double", Double.class);
        registerAlias("float", Float.class);
        registerAlias("boolean", Boolean.class);

        // 注册基本类型包装类数组的别名
        registerAlias("byte[]", Byte[].class);
        registerAlias("long[]", Long[].class);
        registerAlias("short[]", Short[].class);
        registerAlias("int[]", Integer[].class);
        registerAlias("integer[]", Integer[].class);
        registerAlias("double[]", Double[].class);
        registerAlias("float[]", Float[].class);
        registerAlias("boolean[]", Boolean[].class);

        // 注册_基本类型的别名
        registerAlias("_byte", byte.class);
        registerAlias("_long", long.class);
        registerAlias("_short", short.class);
        registerAlias("_int", int.class);
        registerAlias("_integer", int.class);
        registerAlias("_double", double.class);
        registerAlias("_float", float.class);
        registerAlias("_boolean", boolean.class);

        // 注册_基本类型数组的别名
        registerAlias("_byte[]", byte[].class);
        registerAlias("_long[]", long[].class);
        registerAlias("_short[]", short[].class);
        registerAlias("_int[]", int[].class);
        registerAlias("_integer[]", int[].class);
        registerAlias("_double[]", double[].class);
        registerAlias("_float[]", float[].class);
        registerAlias("_boolean[]", boolean[].class);

        // 注册 Date, BigDecimal, Object 等类型的别名
        registerAlias("date", Date.class);
        registerAlias("decimal", BigDecimal.class);
        registerAlias("bigdecimal", BigDecimal.class);
        registerAlias("biginteger", BigInteger.class);
        registerAlias("object", Object.class);

        // 注册 Date, BigDecimal, Object 等数组类型的别名
        registerAlias("date[]", Date[].class);
        registerAlias("decimal[]", BigDecimal[].class);
        registerAlias("bigdecimal[]", BigDecimal[].class);
        registerAlias("biginteger[]", BigInteger[].class);
        registerAlias("object[]", Object[].class);

        // 注册集合类型的别名
        registerAlias("map", Map.class);
        registerAlias("hashmap", HashMap.class);
        registerAlias("list", List.class);
        registerAlias("arraylist", ArrayList.class);
        registerAlias("collection", Collection.class);
        registerAlias("iterator", Iterator.class);

        // 注册 ResultSet 的别名
        registerAlias("ResultSet", ResultSet.class);
    }

    /**
     * throws class cast exception as well if types cannot be assigned
     *
     * @param string
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> Class<T> resolveAlias(String string) {
        try {
            if (string == null) {
                return null;
            }
            // issue #748
            String key = string.toLowerCase(Locale.ENGLISH);
            Class<T> value;
            if (TYPE_ALIASES.containsKey(key)) {
                value = (Class<T>) TYPE_ALIASES.get(key);
            } else {
                value = (Class<T>) Resources.classForName(string);
            }
            return value;
        } catch (ClassNotFoundException e) {
            throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
        }
    }

    /**
     * 1.通过 VFS（虚拟文件系统）获取指定包下的所有文件的路径名，
     *  比如xyz/coolblog/model/Article.class
     * 2.筛选以.class结尾的文件名
     * 3.将路径名转成全限定的类名，通过类加载器加载类名
     * 4.对类型进行匹配，若符合匹配规则，则将其放入内部集合中
     */
    public void registerAliases(String packageName) {
        // 调用重载方法注册别名
        registerAliases(packageName, Object.class);
    }

    /**
     * 会扫描指定包下所有的类，并为指定类的子类添加别名
     * 1.通过 VFS（虚拟文件系统）获取指定包下的所有文件的路径名，
     *   比如xyz/coolblog/model/Article.class
     * 2.筛选以.class结尾的文件名
     * 3.将路径名转成全限定的类名，通过类加载器加载类名
     * 4.对类型进行匹配，若符合匹配规则，则将其放入内部集合中
     *
     * @param packageName
     * @param superType
     */
    public void registerAliases(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        /*
         * 查找指定包下的superType类型类
         * 查找某个包下的父类为 superType 的类。从调用栈来看，这里的
         * superType = Object.class，所以 ResolverUtil 将查找所有的类。
         * 查找完成后，查找结果将会被缓存到内部集合中。
         */
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        // 获取查找结果
        Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
        for (Class<?> type : typeSet) {
            // Ignore inner classes and interfaces (including package-info.java)
            // Skip also inner classes. See issue #6
            // 忽略匿名类，接口，内部类
            // 过滤掉内部类、接口以及抽象类
            if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
                // 为类型注册别名
                registerAlias(type);
            }
        }
    }

    /**
     * 会尝试读取@Alias注解
     *
     * @param type
     */
    public void registerAlias(Class<?> type) {
        // 获取全路径类名的简称:
        // getName() 获取包名+类名；
        // getSimpleName() 获取类名；
        String alias = type.getSimpleName();
        // 读取＠Alias注解
        Alias aliasAnnotation = type.getAnnotation(Alias.class);
        if (aliasAnnotation != null) {
            alias = aliasAnnotation.value();
        }
        // 检测此别名不存在后，会将其记录到TYPE_ALIASES集合中
        // 调用重载方法注册别名和类型映射
        registerAlias(alias, type);
    }

    public void registerAlias(String alias, Class<?> value) {
        if (alias == null) {
            // 检测alias为null, 则直接抛出异常
            throw new TypeException("The parameter alias cannot be null");
        }
        // issue #748 将别名转成小写
        String key = alias.toLowerCase(Locale.ENGLISH);
        /*
         * 检测别名是否已经存在
         * 如果 TYPE_ALIASES 中存在了某个类型映射，这里判断当前类型与映射中的类型是否一致，
         * 不一致则抛出异常，不允许一个别名对应两种类型
         */
        if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
            throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
        }
        // 缓存别名到类型映射
        TYPE_ALIASES.put(key, value);
    }

    public void registerAlias(String alias, String value) {
        try {
            registerAlias(alias, Resources.classForName(value));
        } catch (ClassNotFoundException e) {
            throw new TypeException("Error registering type alias " + alias + " for " + value + ". Cause: " + e, e);
        }
    }

    /**
     * @since 3.2.2
     */
    public Map<String, Class<?>> getTypeAliases() {
        return Collections.unmodifiableMap(TYPE_ALIASES);
    }

}
